//-----------------------------------------------------------
// Usage:
//-----------------------------------------------------------
// (1) Include <script src="pyscript.js"></script> in the
//     HEAD of your document.
//
// (2) Put any pyscript code in a:
//       <script type="text/pyscript">...</script>
//     set of tags.
//
// (3) Profit.
//  
// Pyscript code is just Javascript code where idents matter
// (use only tabs or only spaces) and you don't use parens
// or braces.
//
// TODO:
//  * Handle inline anonymous functions
//  * More robust handling of mixed indents
//  * Switch statements, with statements, etc.
//  * Allow for the case where you indent for code prettification


(function(){
// ==========================

//-----------------------------------------------------------
// Pythonic Javascript (Pyscript) Functionality
//-----------------------------------------------------------


var LineRules = [];
function newLineRule(obj){
  obj.execute = function( line ){
    // Match a line that starts with the token (modulo white-space padding)
    var starter = new RegExp("^[\t ]*\\b"+obj.token+"\\b");
    
    // If this isn't a match to the token, don't modify the line.
    if( !starter.exec(line) ) return null;
    
    // Handle the case of inlining
    if( obj.inlineToken ){
      var split = line.split( obj.inlineToken );
      if( split.length >= 2 && split[1].match(/\w/)){
        line += " }";
      }
    }
        
    // Handle the special case of turning [a,b] into [[a,b]]
    if( typeof(obj.expand[0]) == "string" ){
      obj.expand = [obj.expand];
    }
    
    for(var i=0; i<obj.expand.length;i++){
      var expander = obj.expand[i];
      var what = new RegExp(expander[0]);
      line = line.replace( what, expander[1] );
    }
        
    return line;
  }

  LineRules.push(obj);
  return obj;
}

newLineRule({
  token: "function",
  expand: [":","{"],
  dedent: "}",
  inlineToken: ":"
}); 

newLineRule({
  token: "if",
  expand: [["if","if("], [":"," ){"]],
  dedent: "}",
  inlineToken: ":"
});

newLineRule({
  token: "for",
  expand: [["for","for("], [":"," ){"]],
  dedent: "}",
  inlineToken: ":"
});
  
  
function pyscript(){};
pyscript.prototype = {
  compile: function(input){
    this._indentLevel = 0;
    this._onDedent = [];
    this._indentToken = null;
    this._predent = "";
    
    var lines = input.split(/[\n\r]/g);
    // The predent is just how far the code is indendent as a whole
    // it happens all the time when writing HTML script tags.
    this._predent = this.getPredentFromLines(lines);
    for(var i=0; i<lines.length;i++){
      lines[i] = this.compileLine( lines[i] );
    }
    
    // Deal with any left-over dedents (an issue when EOF is reached)
    if(this._onDedent.length > 0) lines[lines.length-1] += this._onDedent.join("");
    
    return lines.join("\n");
  },
    
  compileLine: function(line){
    // Remove the predent.
    line = this.removePredent(line); 
    
    for(var i=0; i<LineRules.length;i++){
      var rule = LineRules[i];
      var result = rule.execute( line );
      if( result ){
        line = result;
        break;
      }
    }

    var indentLevel = this.getIndentLevel(line);
    if( indentLevel > this._indentLevel ){
      this._indentLevel = indentLevel;
      this._onDedent.push( rule.dedent );      
    }
    if( indentLevel < this._indentLevel ){
      var diff = this._indentLevel - indentLevel;
      this._indentLevel = indentLevel;
      for( var j=0; j<diff; j++){
        line += this._onDedent.pop()
      }
    }
    
    return this.addPredent(line);
  },
  
  getIndentLevel: function(line){
    // First, remove the predent
    
    var padding = line.match( /^[ \t]+/ );

    // If there is no padding, return 0.
    if( !padding ) return 0;

    // If there is no indent style/token set and this is the first indent
    // then we set it!
    if( !this._indentToken ) this._indentToken = padding[0];
    
    var startWhiteSpace = line.split(/\b.*$/)[0];
    return startWhiteSpace.split(this._indentToken).length-1;
  },

  getIndent: function(line){
    var indent = line.match( /^[ \t]+/ );
    if( indent ) return indent[0];
    return "";
  },
  
  getPredentFromLines: function(lines){
    // Return the indent of the first line that has an indent
    // but if we are starting with some text, then there is no
    // predent.
    for(var i=0; i<lines.length; i++){
      var possiblePredent = this.getIndent(lines[i]);
      if( possiblePredent && possiblePredent.length > 0 ) return possiblePredent;
      if( lines[i].match(/^\w/) ) return "";
    }
    return "";
  },
  
  removePredent: function(line){
    if( this._predent ) return line.replace( new RegExp("^"+this._predent), "");
    return line
  },
  
  addPredent: function(line){
    // Only add a predent if there is a predent and it isn't just a blank line
    if( this._predent && line.length > 0 ) return this._predent + line;
    return line;
  }
}
  
  
  
  
function evalPyscript(){
  var compiler = new pyscript();
  
  var scripts = document.getElementsByTagName("script");
  for(var i=0; i<scripts.length; i++){
    if( scripts[i].type.match("pyscript") ){
      var s = document.createElement( "script" );
      s.innerHTML = compiler.compile( scripts[i].innerHTML );
      scripts[i].parentNode.replaceChild( s, scripts[i]);
    }
  }
}



//-----------------------------------------------------------
// Adds domReady functionality.
//-----------------------------------------------------------



if (typeof Event == 'undefined') Event = new Object();

/*
 * Registers function +fn+ will be executed when the dom 
 * tree is loaded without waiting for images. 
 * 
 * Example:
 *
 *  Event.domReady.add(function() {
 *    ...
 *  });
 *
 */
Event.domReady = {
  add: function(fn) {
    
    //-----------------------------------------------------------
    // Already loaded?
    //-----------------------------------------------------------
    if (Event.domReady.loaded) return fn();
    
    //-----------------------------------------------------------
    // Observers
    //-----------------------------------------------------------
    var observers = Event.domReady.observers;
    if (!observers) observers = Event.domReady.observers = [];
    // Array#push is not supported by Mac IE 5
    observers[observers.length] = fn;
    
    //-----------------------------------------------------------
    // domReady function
    //-----------------------------------------------------------
    if (Event.domReady.callback) return;
    Event.domReady.callback = function() {
      if (Event.domReady.loaded) return;
      
      Event.domReady.loaded = true;
      if (Event.domReady.timer) {
        clearInterval(Event.domReady.timer);
        Event.domReady.timer = null;
      }
      
      var observers = Event.domReady.observers;
      for (var i = 0, length = observers.length; i < length; i++) {
        var fn = observers[i];
        observers[i] = null;
        fn(); // make 'this' as window
      }
      Event.domReady.callback = Event.domReady.observers = null;
    };
    
    //-----------------------------------------------------------
    // Emulates 'onDOMContentLoaded'
    //-----------------------------------------------------------
    var ie = !!(window.attachEvent && !window.opera);
    var webkit = navigator.userAgent.indexOf('AppleWebKit/') > -1;
    
    if (document.readyState && webkit) {
      
      // Apple WebKit (Safari, OmniWeb, ...)
      Event.domReady.timer = setInterval(function() {
        var state = document.readyState;
        if (state == 'loaded' || state == 'complete') {
          Event.domReady.callback();
        }
      }, 50);
      
    } else if (document.readyState && ie) {
      
      // Windows IE 
      var src = (window.location.protocol == 'https:') ? '://0' : 'javascript:void(0)';
      document.write(
        '<script type="text/javascript" defer="defer" src="' + src + '" ' + 
        'onreadystatechange="if (this.readyState == \'complete\') Event.domReady.callback();"' + 
        '><\/script>');
      
    } else {
      
      if (window.addEventListener) {
        // for Mozilla browsers, Opera 9
        document.addEventListener("DOMContentLoaded", Event.domReady.callback, false);
        // Fail safe 
        window.addEventListener("load", Event.domReady.callback, false);
      } else if (window.attachEvent) {
        window.attachEvent('onload', Event.domReady.callback);
      } else {
        // Legacy browsers (e.g. Mac IE 5)
        var fn = window.onload;
        window.onload = function() {
          Event.domReady.callback();
          if (fn) fn();
        }
      }
      
    }
    
  }
}

//-----------------------------------------------------------
// Setup
//-----------------------------------------------------------

  
window.Pyscript = new pyscript();
window.Pyscript.eval = evalPyscript;

// For unit-test purposes
window.Pyscript.getLineRule = function(token){
  for(var i=0;i<LineRules.length;i++){
    if( LineRules[i].token == token ) return LineRules[i];
  }
};

Event.domReady.add(function() {
  evalPyscript();
});


// ==========================  
})();